#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <assert.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>

#define MAX 1024 //最多1024个字符
#define ARGC 64 //最多63个选项(除去换行带来的空格)
#define SPG " " //选项之间的分隔符

extern char **environ;

enum redir
{
    REDIR_INPUT = 0, //输入重定向
    REDIR_OUTPUT,    //输出重定向
    REDIR_APPEND,    //追加重定向
    REDIR_NONE       //无重定向
};

char *checkDir(char commandstr[], enum redir *redir_type)
{
    //1. 检测commandstr内部是否有 > >> <
    //2. 如果有要根据> >> < 设置redir_type = ?
    char* filename = NULL;
    int len = strlen(commandstr)-1;
    int i = 0;
    int flag = 0;
    while(i<len)
    {
        if(commandstr[i] == '<')
        {
            *redir_type = REDIR_INPUT;
            flag = 1;
            break;
        }
        else if(commandstr[i] == '>')
        {
            if(i+1<len && commandstr[i+1] == '>')
            {
                *redir_type = REDIR_APPEND;
                flag = 1;
                commandstr[i++] = '\0';
            }
            else
            {
                *redir_type = REDIR_OUTPUT;
                flag = 1;
            }
            break;
        }
        ++i;
    }

    //3. 将 > >> < -> \0， 将commandstr设置成为两部分
    //4. 保存文件名，并返回
    if(flag)
    {
        commandstr[i++] = '\0';
        while(i<len)
        {
            if(commandstr[i++] != ' ')
            {
                return commandstr+i-1;
            }
        }
    }
    //5. 如果上面不满足，直接返回
    return NULL;
}

void debugprint(char* argv[])
{
    int i;
    for(i=0;argv[i];++i)
    {
        printf("%s",argv[i]);
    }
}

void Printenv()
{
    int i = 0;
    while(environ[i])
    {
        printf("%s\n",environ[i++]);
    }
}


//实现bash可以识别用户名和当前文件夹，存在一定bug
int main()
{
    char my_env[32][256] = {0}; //最多存储256条环境变量
    int env_index = 0;
    char* LastPATH = NULL;
    int last_exit = 0;
    while(1)
    {
        char str[MAX] = {0}; //存放命令字符
        char *argv[ARGC] = {NULL};
        printf("[ARMCSKGT@LinuxMyBashTest BASH]# ");
        fflush(stdout);
        char* s = fgets(str,sizeof(str),stdin);
        assert(s);
        if(*s == '\n') continue;

        str[strlen(str)-1]='\0';
        (void*)s; // 保证在release方式发布的时候，因为去掉assert了，所以s就没有被使用，而带来的编译告警, 什么都没做，但是充当一次使用

        //判断重定向
        enum redir r = REDIR_NONE;
        const char* filename = checkDir(str,&r);
        
        int i = 0;
        argv[i++] = strtok(str,SPG);
        while((argv[i++] = strtok(NULL,SPG)) && i<ARGC-1);
        
        //debugprint(argv); //测试命令截取情况



        //特殊情况处理 - 内建指令处理
        if(strcmp(argv[0],"ls") == 0) //ls指令区分文件夹和文件等配色方案
        {
            int pos = 0;
            while(argv[pos++]) {}
            argv[pos-1] = "--color=auto";
            argv[pos] = NULL;
        }

        if(strcmp(argv[0],"cd") == 0)
        {
            if(argv[1]!=NULL)
            {
                if(strcmp(argv[1],"~") == 0)
                {
                    LastPATH = getenv("PWD");
                    chdir(getenv("HOME"));
                } 
                else if(strcmp(argv[1],"-") == 0 && LastPATH)
                {
                    char* tmp = LastPATH;
                    LastPATH = getenv("PWD");
                    chdir(tmp);
                }
                else
                {
                    LastPATH = getenv("PWD");
                    chdir(argv[1]);
                }
            }
            continue;
        }
        else if(strcmp(argv[0],"export") == 0)
        {
            if(argv[1] != NULL)
            {
                if(env_index == 31) env_index=0;
                strcpy(my_env[env_index],argv[1]);
                putenv(my_env[env_index]);
                ++env_index;
            }
            continue;
        }
        else if(strcmp(argv[0],"env") == 0)
        {
            Printenv();
            continue;
        }
        else if(strcmp(argv[0],"echo") == 0)
        {
            if(*argv[1] == '$')
            {
                if(*(argv[1]+1) == '?')
                {
                    printf("%d:%s\n",last_exit,strerror(last_exit));
                    continue;
                }
                else
                {
                    const char* target = getenv(argv[1]+1);
                    if(target)
                    {
                        printf("%s=%s\n",argv[1]+1,target);
                        continue;
                    }
                }
            }
        }

        pid_t id = fork();
        assert(id>=0);
        (void)id;
        if(id == 0)
        {
            if(r != REDIR_NONE)
            {
                switch(r)
                {
                    case REDIR_INPUT: //输入重定向<
                    {
                        int fd = open(filename,O_RDONLY);
                        if(fd == -1)
                        {
                            printf("bash: %s: No such file or directory\n",filename);
                            continue; //当重定向失败就不需要进程替换了
                        }
                        else
                        {
                            dup2(fd,0);
                        }
                        break;
                    }
                    case REDIR_OUTPUT: //输出重定向>
                    {
                        dup2(open(filename,O_WRONLY|O_CREAT|O_TRUNC,0666),1);
                        break;
                    }
                    case REDIR_APPEND: //追加重定向>>
                    {
                        dup2(open(filename,O_WRONLY|O_CREAT|O_APPEND,0666),1);
                        break;
                    }
                }
            }
            execv(argv[0], argv);
            execvp(argv[0], argv);
            printf("指令错误!\n");
            exit(-1);
        }
        int status = 0;
        waitpid(id,&status,0);
        last_exit = WEXITSTATUS(status);
    }
    return 0;
}

//去除导航功能，增加内建命令
